﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application.Model;
using System.Collections.Concurrent;
using System.Threading;
using Upreader.Usenet.Nntp;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using Upreader.Common;

namespace Upreader.Engine
{
    public class ConnectionProcessor 
    {
        #region Fields

        private readonly Server _serverInformation;
        private readonly DownloadEngineContext _context;

        private INntpClient _nntpClient;
        private Task _connectionTask;
        private CancellationTokenSource _cancellationTokenSource;
        private bool _enabled;
        private Exception _exception;

        #endregion // Fields

        #region Constructor

        public ConnectionProcessor(Server serverInformation,
            DownloadEngineContext context)
        {
            if (serverInformation == null)
                throw new ArgumentNullException("serverInformation");
            if (context == null)
                throw new ArgumentNullException("context"); ;

            _serverInformation = serverInformation;
            _context = context;
        }

        #endregion // Constructor

        #region Properties

        /// <summary>
        /// Get or set wether this connection is enabled
        /// </summary>
        public bool Enabled
        {
            get { return _enabled; }
            set 
            { 
                _enabled = value;

                if (!IsRunning && _enabled)
                {
                    Start();
                }
                else if (IsRunning && !_enabled)
                {
                    Stop();
                }
            }
        }

        public INntpClient NntpClient
        {
            get { return _nntpClient; }
        }

        public bool IsRunning
        {
            get { return _connectionTask != null;  }
        }

        public Exception Exception
        {
            get { return _exception; }
            set
            {
                _exception = value;

                if (_exception != null)
                {
                    EventHandler handler = OnFaulted;
                    if (handler != null)
                        handler(this, EventArgs.Empty);
                }
            }
        }

        public bool IsFaulted
        {
            get { return _exception != null; }
        }

        #endregion // Properties

        #region Methods

        public Task Stop()
        {
            if (!IsRunning)
                throw new InvalidOperationException("Connection is not started");

            _cancellationTokenSource.Cancel();

            Task closingTask = Task.Factory.StartNew(() =>
            {
                // wait for the connection task to finish
                _connectionTask.Wait();
            }, TaskCreationOptions.AttachedToParent);

            return closingTask;
        }

        public void Start()
        {
            if (IsRunning)
                throw new InvalidOperationException("Connection is already started"); 

            _cancellationTokenSource = new CancellationTokenSource();
            _connectionTask = Run(_cancellationTokenSource.Token).ContinueWith(result =>
            {
                if (result.IsFaulted)
                {
                    Exception = result.Exception;
                }
            });
        }

        #endregion // Methods

        #region Protected Methods

        protected virtual INntpClient CreateNntpClient()
        {
            INntpClient client = new NntpClient()
            {
                Address = _serverInformation.Address,
                Port = _serverInformation.Port,
                IsSecuredConnection = _serverInformation.IsSecuredConnection
            };

            if (_serverInformation.IsAuthenticationRequired)
            {
                client.Authentication = new NntpClientAuthentication(_serverInformation.UserName, _serverInformation.Password);
            }

            return client;
        }

        protected Task Run(CancellationToken cancellationToken)
        {
            _nntpClient = CreateNntpClient();

            _connectionTask = Task.Factory.StartNew(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();

                Trace.WriteLine(string.Format("Connecting to server {0}...", _serverInformation.Title), Resources.TraceCategory_Connection);

                // Open the client
                NntpClient.Open();

                Trace.WriteLine(string.Format("Connection for server {0} established", _serverInformation.Title), Resources.TraceCategory_Connection);

                try
                {
                    foreach (DownloadSegmentContext downloadSegmentContext in _context.UnhandeledSegmentContextCollection.GetConsumingEnumerable(cancellationToken))
                    {
                        string nextArticleId = downloadSegmentContext.Segment.ArticleId;

                        try
                        {
                            // load the article body stream
                            using (Stream articleBodyStream = NntpClient.GetArticleBodyStream(nextArticleId))
                            {
                                // store the segment here since we want to reuse this connection asap in not nessesarily go back to the file system
                                ChunkedMemoryStream downloadedStream = new ChunkedMemoryStream();
                                articleBodyStream.CopyTo(downloadedStream, downloadedStream.ChunkSize);
                                downloadedStream.Position = 0;
                                downloadSegmentContext.DataStream = downloadedStream;
                            }

                            Trace.WriteLine(string.Format("Fetched segment {0} for file {1} ({2} bytes)", downloadSegmentContext.Segment.Index, downloadSegmentContext.Segment.FileId, downloadSegmentContext.DataStream.Length), Resources.TraceCategory_Connection);

                            downloadSegmentContext.State = DownloadSegmentContextState.Caching;
                        }
                        catch (NntpException ex)
                        {
                            Trace.WriteLine(string.Format("Failed fetching segment {0} for file {1} ({2})", downloadSegmentContext.Segment.Index, downloadSegmentContext.Segment.FileId, ex.Message), Resources.TraceCategory_Connection);

                            int responseCode = ex.ResponseMessage.ParseResponseCode();
                            switch (responseCode)
                            {
                                case (int)Rfc977ResponseCode.NoSuchArticleFound:
                                case (int)Rfc977ResponseCode.NoSuchArticleNumber:
                                case (int)Rfc977ResponseCode.NoSuchNewsgroup:
                                    downloadSegmentContext.State = DownloadSegmentContextState.Unavailable;
                                    break;
                                default:
                                    throw;
                            }
                        }

                        // write to output
                        _context.DownloadedSegmentContextCollection.Add(downloadSegmentContext, cancellationToken);
                    }
                }
                finally
                {
                    // Always close
                    NntpClient.Close();

                    Trace.WriteLine(string.Format("Connection for server {0} closed", _serverInformation.Title), Resources.TraceCategory_Connection);
                }
            }, cancellationToken, TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning, TaskScheduler.Current);

            return _connectionTask;
        }

        #endregion // Protected Methods

        #region Events

        public event EventHandler OnFaulted;

        #endregion // Events
    }
}
